package com.opdar.platform.core.base;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.opdar.platform.annotations.Request;
import com.opdar.platform.core.convert.DefaultJSONConvert;
import com.opdar.platform.core.convert.JSONConvert;
import com.opdar.platform.core.exceptions.CryptoAuthException;
import com.opdar.platform.core.session.ISessionManager;
import com.opdar.platform.utils.AES;
import com.opdar.platform.utils.Base64;
import com.opdar.platform.utils.Utils;
import com.sun.jndi.toolkit.url.Uri;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

/**
 * Created by shiju on 2017/1/18.
 */
public class DispatcherServlet extends GenericServlet {
    private final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        if (servletRequest instanceof HttpServletRequest) {
            final Map<String, Object> parameterMap = new HashMap<String, Object>();
            parameterMap.putAll(servletRequest.getParameterMap());
            ServletContext servletContext = ((HttpServletRequest) servletRequest).getSession().getServletContext();
            ApplicationContext applicationContext = (ApplicationContext) servletContext.getAttribute(Context.APPLICATION_CONTEXT);
            String requestURI = ((HttpServletRequest) servletRequest).getRequestURI();
            String contextPath = ((HttpServletRequest) servletRequest).getContextPath();
            String routerName = Utils.testRouter(requestURI.replaceFirst(contextPath,""));
            routerName = URLDecoder.decode(routerName,"UTF-8");
            int responseCode = HttpServletResponse.SC_NOT_FOUND;
            try {
                Context.REQUEST.set(servletRequest);
                Context.RESPONSE.set(servletResponse);

                if (ServletFileUpload.isMultipartContent((HttpServletRequest) servletRequest)) {
                    ServletFileUpload servletFileUpload = applicationContext.getBean(ServletFileUpload.class);
                    Map<String, List<FileItem>> map = servletFileUpload.parseParameterMap((HttpServletRequest) servletRequest);
                    for (Map.Entry<String, List<FileItem>> entry : map.entrySet()) {
                        LinkedList<Object> list = new LinkedList<Object>();
                        boolean isFormField = false;
                        if (entry.getValue().size() > 0) {
                            for (FileItem fileItem : entry.getValue()) {
                                isFormField = fileItem.isFormField();
                                if (isFormField) {
                                    list.add(fileItem.getString());
                                } else {
                                    list.add(fileItem);
                                }
                            }
                        }
                        parameterMap.put(entry.getKey(), isFormField ? list.toArray(new String[0]) : list.toArray(new FileItem[0]));
                    }
                }
                int version = getVersion(parameterMap);

                Router _r = new Router(routerName);
                _r.setVersion(version);
                String _m = ((HttpServletRequest) servletRequest).getMethod();
                if (_m != null) {
                    _r.setMethod(Request.Method.valueOf(_m));
                }
                if (_r.getMethod() == Request.Method.OPTIONS && RouterManager.contains(_r,parameterMap)) {
                    responseCode = 200;
                    return;
                }
                if (RouterManager.contains(_r,parameterMap)) {
                    Router router = RouterManager.get(_r);
                    if (router == null) {
                        return;
                    }
                    RouterManager.setCurrentRouter(router);
                    responseCode = HttpServletResponse.SC_OK;
                    try {
                        boolean ret = router.interceptorBefore(new InterceptorCallback() {
                            @Override
                            public void handle(Map<String, Object> parameters) {
                                parameterMap.putAll(parameters);
                            }
                        });
                        if (!ret) return;
                        if (router.isHasToken()) {
                            boolean authentification = false;
                            try {
                                ISessionManager sessionManager = applicationContext.getBean(ISessionManager.class);
                                //验证Token
                                String token = getParameterString(parameterMap, "token");
                                if (StringUtils.isEmpty(token)) {
                                    token = (String) ((HttpServletRequest) servletRequest).getSession().getAttribute("token");
                                    if (StringUtils.isEmpty(token)) {
                                        return;
                                    }
                                }
                                if (sessionManager == null) {
                                    return;
                                } else {
                                    Object user = sessionManager.get(token);
                                    if (user != null) {
                                        authentification = true;
                                        sessionManager.clearTimeout(token);
                                    } else {
                                        return;
                                    }
                                }
                            } catch (NoSuchBeanDefinitionException e) {
                                logger.info("SessionManager 没有初始化!");
                                return;
                            } finally {
                                if (!authentification) {
                                    logger.info("验证未通过");
                                }
                            }
                        }
                        if (router.getSecretType() != SecretType.NONE) {
                            decrypt(servletRequest, _r, router, parameterMap);
                        }
                        Object controller = applicationContext.getBean(router.getHandler().getParentClass());
                        Object o = null;
                        o = router.getHandler().invoke(controller, parameterMap);
                        ret = router.interceptorAfter();
                        if (!ret) return;
                        if (router.getHandler().getReturnType() != void.class) {
                            if (router.getFormat() == Request.Format.VIEW) {
                                if (!StringUtils.isEmpty(o) && o.toString().startsWith("redirect:")) {
                                    ((HttpServletResponse) servletResponse).sendRedirect(o.toString().substring(9));
                                } else {
                                    try {
                                        ViewTemplate viewTemplate = applicationContext.getBean(ViewTemplate.class);
                                        viewTemplate.render(o.toString(), servletRequest, servletResponse);
                                    } catch (NoSuchBeanDefinitionException e) {
                                        logger.info("ViewTemplate 没有初始化!");
                                    }
                                }
                            } else {
                                byte[] bytes = getReturnBytes(applicationContext, parameterMap, router, o);
                                if (bytes != null) {
                                    servletResponse.getOutputStream().write(bytes);
                                }
                            }
                        } else {
                            logger.info("警告! [{}] 返回类型为空(void).Class:[{}].Method:[{}] ", requestURI, router.getHandler().getParentClass(), router.getHandler().getMethodName());
                        }
                    } catch (Throwable throwable) {
                        List<ErrorHandler> errHandlers = router.getHandler().getErrorHandlers();
                        for (ErrorHandler errorHandler : errHandlers) {
                            errorHandler.handle(throwable);
                        }
                    } finally {
                        RouterManager.clearCurrentRouter();
                        router.interceptorFinish();
                    }
                } else {
                    if (routerName.length() > 1) {
                        String[] path = routerName.substring(1).split("/");
                        String path1 = path[0];
                        Map<String, String> resources = Context.RESOURCE_MAPPING;
                        if (resources != null && resources.containsKey("/".concat(path1))) {
                            String prefix = resources.get("/".concat(path1));
                            path[0] = prefix;
                            String resourcePath = StringUtils.arrayToDelimitedString(path, "/");

                            Resource resource = null;
                            if (resourcePath.startsWith("classpath")) {
                                resource = applicationContext.getResource(resourcePath);
                            } else {
                                try{
                                    URL url = servletContext.getResource(resourcePath);
                                    if (url != null) {
                                        resource = new UrlResource(url);
                                    }else{
                                        throw new Exception();
                                    }
                                }catch (Exception e){
                                    resource = new UrlResource("file:"+resourcePath);
                                }
                            }
                            if (resource != null && resource.isReadable()) {
                                responseCode = HttpServletResponse.SC_OK;
                                byte[] b = new byte[1024];
                                int len = -1;
                                servletResponse.setContentType(servletRequest.getContentType());
                                InputStream inputStream = resource.getInputStream();
                                while ((len = inputStream.read(b)) != -1) {
                                    servletResponse.getOutputStream().write(b, 0, len);
                                }
                                inputStream.close();
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Resource [{}] 加载完成 . ", resourcePath);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                logger.info("[{}]请求错误.Code:500.Error:{}", requestURI, e);
            } finally {
                Context.REQUEST.remove();
                Context.RESPONSE.remove();
                if (servletResponse instanceof HttpServletResponse && responseCode != HttpServletResponse.SC_OK) {
                    ((HttpServletResponse) servletResponse).sendError(responseCode);
                    logger.info("[{}].Code:{}.Error.", requestURI, responseCode);
                }
                servletResponse.flushBuffer();
            }
        }
    }

    private String getParameterString(Map<String, Object> parameterMap, String key) {
        String result = null;
        if (parameterMap.containsKey(key)) {
            Object p = parameterMap.get(key);
            if (p instanceof String[]) {
                result = ((String[]) p)[0];
            } else if (p instanceof String) {
                result = (String) p;
            }
        }
        return result;
    }

    /**
     * 获取返回内容
     *
     * @param applicationContext
     * @param parameterMap
     * @param router
     * @param o
     * @return
     * @throws JsonProcessingException
     */
    private byte[] getReturnBytes(ApplicationContext applicationContext, Map<String, Object> parameterMap, Router router, Object o) throws Exception {
        byte[] bytes = null;
        Request.Format format = getFormat(parameterMap, router);
        if (format == Request.Format.XML) {
            XmlMapper xml = new XmlMapper();
            bytes = xml.writeValueAsBytes(o);
        } else if (format == Request.Format.JSON) {
            JSONConvert convert = new DefaultJSONConvert();
            try {
                convert = applicationContext.getBean(JSONConvert.class);
            } catch (Exception ignored) {
            }
            bytes = convert.serialization(o);
        } else if (format == Request.Format.STREAM) {
            if (o instanceof ByteArrayOutputStream) {
                bytes = ((ByteArrayOutputStream) o).toByteArray();
            } else if (o instanceof byte[]) {
                bytes = (byte[]) o;
            }
        }
        return bytes;
    }

    /**
     * 加密處理
     *
     * @param servletRequest
     * @param _r
     * @param router
     * @param parameterMap
     * @throws Exception
     */
    private void decrypt(ServletRequest servletRequest, Router _r, Router router, Map<String, Object> parameterMap) throws Exception {
        try {
            byte[] secretBytes = null;
            if (_r.getMethod() == Request.Method.GET) {
                secretBytes = Base64.decode(getParameterString(parameterMap, "request").getBytes());
            } else {
                ServletInputStream inputStream = servletRequest.getInputStream();
                secretBytes = Base64.decode(StreamUtils.copyToByteArray(inputStream));
            }
            if (secretBytes.length == 0) {
                return;
            }
            switch (router.getSecretType()) {
                case AES:
                    secretBytes = AES.decrypt(secretBytes, router.getSecretKey().getBytes());
                    break;
            }
            if (secretBytes != null) {
                Map<String, Object> parameterMap2 = new HashMap<String, Object>();
                String s = new String(secretBytes);
                String[] keyValues = s.split("&");
                for (String p : keyValues) {
                    String[] s2 = p.split("=");
                    String value = null;
                    if (s2.length > 1) {
                        value = s2[1];
                    }
                    String key = s2[0];
                    String[] parameterValues = null;
                    if (parameterMap2.containsKey(key)) {
                        Object _parameterValues = parameterMap2.get(key);
                        if (_parameterValues instanceof String[]) {
                            parameterValues = (String[]) _parameterValues;
                        } else {
                            continue;
                        }
                    } else {
                        parameterMap2.put(key, parameterValues = new String[0]);
                    }
                    String[] dest = new String[parameterValues.length + 1];
                    dest[dest.length - 1] = value;
                    System.arraycopy(parameterValues, 0, dest, 0, parameterValues.length);
                    parameterMap2.put(key, dest);
                }
                parameterMap.putAll(parameterMap2);
            }
        } catch (Exception e) {
            throw new CryptoAuthException("参数解密失败.", e);
        }
    }

    /**
     * 輸出格式獲取
     *
     * @param parameterMap
     * @param router
     * @return
     */
    private Request.Format getFormat(Map<String, Object> parameterMap, Router router) {
        String _f = getParameterString(parameterMap, "format");
        Request.Format format = router.getFormat();
        if (_f != null) {
            format = Request.Format.valueOf(_f);
        }
        return format;
    }

    /**
     * 获取接口版本
     *
     * @param parameterMap
     * @return
     */
    private int getVersion(Map<String, Object> parameterMap) {
        int version = 0;
        try {
            String _v = getParameterString(parameterMap, "version");
            if (_v != null) {
                version = Integer.valueOf(_v);
            }
        } catch (NumberFormatException e) {
            logger.info("no version . default set 0 .");
        }
        return version;
    }
}
